﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Windows.Threading;
using FluentValidation.Results;

namespace LINQPad.OpenAccess.UI.ViewModel
{
    /// <summary>
    /// Abstract base class for validatable view models
    /// </summary>
    public abstract class ValidatableViewModelBase : ViewModelBase, IDataErrorInfo
    {
        #region Private

        #region - Vars

        private DispatcherTimer __delayNotificationsTimer;
        private bool _raisePropertyChangedValidationDependentProperties;

        #endregion

        #region - Functions

        /// <summary>
        /// Creates the delay notifications timer.
        /// </summary>
        /// <returns>The timer.</returns>
        private DispatcherTimer CreateDelayNotificationsTimer( )
        {
            var timer = new DispatcherTimer( );
            timer.IsEnabled = false;
            timer.Interval = TimeSpan.FromMilliseconds( 200 );
            timer.Tick += DelayNotificationsTimer_Tick;
            return timer;
        }
        /// <summary>
        /// Delays the notification of the validation dependent property change.
        /// </summary>
        private void DelayValidationDependentPropertyChangedNotification( )
        {
            var timer = this.__delayNotificationsTimer ?? ( this.__delayNotificationsTimer = this.CreateDelayNotificationsTimer( ) );
            timer.Stop( );
            timer.Start( );
        }
        /// <summary>
        /// Releases the delay notifications timer.
        /// </summary>
        /// <param name="timer">The timer.</param>
        private void ReleaseDelayNotificationsTimer( ref DispatcherTimer timer )
        {
            if ( timer != null )
            {
                timer.Stop( );
                timer.Tick -= DelayNotificationsTimer_Tick;
                timer = null;
            }
        }

        #endregion

        #region - Event Handlers

        /// <summary>
        /// Handles the Tick event of the DelayNotificationsTimer.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        private void DelayNotificationsTimer_Tick( object sender, EventArgs e )
        {
            ( ( DispatcherTimer ) sender ).Stop( );
            this.OnValidationDependentPropertyChanged( );
        }

        #endregion

        #endregion

        #region Protected

        #region - Functions

        /// <summary>
        /// Validates the current instance.
        /// </summary>
        /// <param name="propertyName">Name of the property or <c>null</c>, if all properties should be validated.</param>
        /// <returns>
        /// The validation result or <c>null</c>.
        /// </returns>
        protected abstract ValidationResult ValidateInstance( string propertyName = null );
        /// <summary>
        /// Gets the validation error message for the specified validation result.
        /// </summary>
        /// <param name="result">The result.</param>
        /// <param name="propertyName">Name of the property or <c>null</c>, if all properties were validated.</param>
        /// <returns>
        /// The error message or an empty string.
        /// </returns>
        protected abstract string GetValidationErrorMessage( ValidationResult result, string propertyName = null );
        /// <summary>
        /// Called when a validation dependent property has changed.
        /// </summary>
        protected virtual void OnValidationDependentPropertyChanged( )
        {
            base.OnPropertyChanged( ( ) => this.IsValid );
            base.OnPropertyChanged( ( ) => this.Error );
        }

        #endregion

        #region - Properties

        /// <summary>
        /// Gets a value indicating whether property changed notifications should be raised for validation dependent properties.
        /// <para>(e.g. like IsValid, Error)</para>
        /// </summary>
        /// <value>
        /// <c>true</c> if property changed notifications should be raised; otherwise, <c>false</c>.
        /// </value>
        protected bool RaisePropertyChangedValidationDependentProperties
        {
            get
            {
                return this._raisePropertyChangedValidationDependentProperties;
            }
            set
            {
                if ( this._raisePropertyChangedValidationDependentProperties != value )
                {
                    this._raisePropertyChangedValidationDependentProperties = value;
                    if ( !value )
                        this.ReleaseDelayNotificationsTimer( ref this.__delayNotificationsTimer );
                }
            }
        }

        #endregion

        #endregion

        #region Public

        #region - Functions

        /// <summary>
        /// Invalidates this instance.
        /// </summary>
        public virtual void Invalidate( )
        {
            if ( this._raisePropertyChangedValidationDependentProperties )
                this.OnValidationDependentPropertyChanged( );
        }

        #endregion

        #region - Properties

        /// <summary>
        /// Gets a value indicating whether this instance is valid.
        /// </summary>
        /// <value>
        ///   <c>true</c> if this instance is valid; otherwise, <c>false</c>.
        /// </value>
        public bool IsValid
        {
            get
            {
                var result = this.ValidateInstance( );
                return result == null || result.IsValid;
            }
        }

        #endregion

        #endregion

        #region ViewModelBase Members

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected override void Dispose( bool disposing )
        {
            if ( disposing )
            {
                this.ReleaseDelayNotificationsTimer( ref this.__delayNotificationsTimer );
                this._raisePropertyChangedValidationDependentProperties = false;
            }
            base.Dispose( disposing );
        }
        /// <summary>
        /// Called when a property has changed.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        protected override void OnPropertyChanged( string propertyName )
        {
            base.OnPropertyChanged( propertyName );
            if ( this._raisePropertyChangedValidationDependentProperties )
                this.DelayValidationDependentPropertyChangedNotification( );
        }

        #endregion

        #region IDataErrorInfo Members

        /// <summary>
        /// Gets an error message indicating what is wrong with this object.
        /// </summary>
        /// <returns>An error message indicating what is wrong with this object. The default is an empty string ("").</returns>
        public string Error
        {
            get
            {
                var result = this.ValidateInstance( );
                return this.GetValidationErrorMessage( result );
            }
        }
        /// <summary>
        /// Gets the error message for the property with the given name.
        /// </summary>
        /// <param name="columnName">Name of the column.</param>
        /// <returns>
        /// The error message or an empty string.
        /// </returns>
        public string this[string columnName]
        {
            get
            {
                var result = this.ValidateInstance( String.IsNullOrWhiteSpace( columnName ) ? null : columnName );
                return this.GetValidationErrorMessage( result, columnName );
            }
        }

        #endregion
    }

}
